package com.tbit.main.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.tbit.common.constant.CommonConstant;
import com.tbit.main.constant.Constant;
import com.tbit.main.constant.DaoParamConstant;
import com.tbit.main.controller.interceptor.LoginUserContextHolder;
import com.tbit.main.dao.capital.RideLogDetailsDao;
import com.tbit.main.dao.core.UpKeepConfigDao;
import com.tbit.main.dao.core.UpKeepDao;
import com.tbit.main.pojo.*;
import com.tbit.main.pojo.VO.*;
import com.tbit.main.service.*;
import com.tbit.main.util.DateTimeUtil;
import com.tbit.main.util.ListUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author yao
 * @since 2025/1/22 17:21
 */
@Service
public class UpKeepServiceImpl implements UpKeepService {

    @Resource
    private UpKeepDao upKeepDao;

    @Resource
    private MachineService machineService;

    @Resource
    private UpKeepLogService upKeepLogService;

    @Resource
    private UpKeepTypeLogService upKeepTypeLogService;

    @Resource
    private AccountService accountService;

    @Resource
    private GeoreGionService georeGionService;

    @Resource
    private GeoreMachineService georeMachineService;

    @Resource
    private CalibrateService calibrateService;

    @Resource
    private MachineUsageSumStatService machineUsageSumStatService;
    @Autowired
    private RideLogDetailsDao rideLogDetailsDao;
    @Autowired
    private UpKeepConfigDao upKeepConfigDao;

    @Override
    public List<UpKeepRespVO> getList(UpKeepQueryReqVO reqVO) {
        //前端传过来的是km，兼容单位不一致问题转m（app已全量升级，返工升级很麻烦，因此后端兼容）
        reqVO.KMConvertM();
        List<UpKeepRespVO> list = upKeepDao.selectList(reqVO);

        Date now = new Date();
        list.forEach(upKeep -> {
                upKeep.setTime(DateUtil.between(Optional.ofNullable(upKeep.getDateTime()).orElse(now), now, DateUnit.DAY));
                //M转KM
                upKeep.MConvertKM();
        });

        /* 校准经纬度 */
        calibrateService.calibrateUpKeeps(list, reqVO.getMapType());
        return list;
    }

    @Override
    public List<UpKeep> getList(Integer accountId, List<Integer> machineIds) {
        return CollUtil.isEmpty(machineIds)
                ? Collections.emptyList()
                : upKeepDao.selectList2(accountId, machineIds);
    }

    @Override
    public UpKeepPutMachineRespVO putUpkeep(PutMachineReqVO reqVO) {
        List<String> userCodes = ListUtil.convertList(reqVO.getUserCodes(), String::valueOf);
        List<Machine> machines = machineService.getByUserCodesAndAccountId(userCodes, reqVO.getAccountId());

        Map<String, String> errorResult = machineService.checkMachine(userCodes, machines);

        machines = machines.stream().filter(machine -> !errorResult.containsKey(machine.getUserCode())).collect(Collectors.toList());

        List<Integer> queryMachineIds = machines.stream().map(Machine::getMachineId).collect(Collectors.toList());

        List<UpKeep> list = getList(reqVO.getAccountId(), queryMachineIds);
        Map<Integer, UpKeep> upKeepTable = list.stream().collect(Collectors.toMap(UpKeep::getMachineId, Function.identity()));

        UpKeepPutMachineRespVO upKeepPutMachine = new UpKeepPutMachineRespVO();

        Date now = new Date();
        List<UpKeepRespVO> upKeeps = machines.stream().map(machine -> {
            UpKeep upKeep = upKeepTable.getOrDefault(machine.getMachineId(), new UpKeep());
            UpKeepRespVO result = new UpKeepRespVO();
            result.setMachineId(upKeep.getMachineId());
            result.setStatus(upKeep.getStatus());
            result.setUserCode(machine.getUserCode());
            //M转KM
            result.MConvertKM();
            result.setTime(DateUtil.between(Optional.ofNullable(upKeep.getDateTime()).orElse(now), now, DateUnit.DAY));
            return result;
        }).collect(Collectors.toList());

        upKeepPutMachine.setPutFailure(PutMachineRespVO.of(errorResult));
        upKeepPutMachine.setPutSuccess(upKeeps);
        return upKeepPutMachine;
    }

    @Override
    public List<PutMachineRespVO> batchUpkeep(UpKeepReqVO reqVO) {
        List<String> userCodes = ListUtil.convertList(reqVO.getUserCodes(), String::valueOf);
        List<Machine> machines = machineService.getByUserCodesAndAccountId(userCodes, reqVO.getAccountId());

        Map<String, String> errorResult = machineService.checkMachine(userCodes, machines);

        machines = machines.stream().filter(machine -> !errorResult.containsKey(machine.getUserCode())).collect(Collectors.toList());

        List<Integer> queryMachineIds = machines.stream().map(Machine::getMachineId).collect(Collectors.toList());

        List<UpKeep> list = getList(reqVO.getAccountId(), queryMachineIds);

        Map<Integer, UpKeep> upKeepTable = list.stream().collect(Collectors.toMap(UpKeep::getMachineId, Function.identity()));

        if (CollUtil.isNotEmpty(upKeepTable)) {
            // 批量删除,修改状态为无需保养
            upKeepDao.cleanBatch(upKeepTable.values());
        }
        //保养
        upKeepTable = this.completeUpKeep(upKeepTable,machines);
        // 记录保养日志
        List<UpKeepLog> upKeepLogs = getUpKeepLogs(reqVO, machines, upKeepTable);
        upKeepLogService.saveBatch(upKeepLogs);

        saveBatchUpkeepTypeLog(reqVO, upKeepLogs);
        return PutMachineRespVO.of(errorResult);
    }

    private void saveBatchUpkeepTypeLog(UpKeepReqVO reqVO, List<UpKeepLog> upKeepLogs) {
        if (CollUtil.isEmpty(upKeepLogs) || StrUtil.isBlank(reqVO.getUpKeepTypes())) {
            return;
        }

        Date now = new Date();
        AccountUser loginUser = LoginUserContextHolder.getLoginUser();

        List<Integer> upKeepTypes = ListUtil.convertList(reqVO.getUpKeepTypes(), Integer::valueOf);
        List<UpKeepTypeLog> savedUpKeepTypeLogs = new ArrayList<>();
        for (Integer upKeepType : upKeepTypes) {

            List<UpKeepTypeLog> upKeepTypeLogs = upKeepLogs.stream().map(upKeepLog -> {
                UpKeepTypeLog upKeepTypeLog = new UpKeepTypeLog();
                upKeepTypeLog.setUpKeepLogId(upKeepLog.getId());
                upKeepTypeLog.setUpKeepType(upKeepType);
                upKeepTypeLog.setCreator(String.valueOf(loginUser.getAccountUserId()));
                upKeepTypeLog.setCreateTime(now);
                upKeepTypeLog.setUpdater(String.valueOf(loginUser.getAccountUserId()));
                upKeepTypeLog.setUpdateTime(now);
                return upKeepTypeLog;
            }).collect(Collectors.toList());

            savedUpKeepTypeLogs.addAll(upKeepTypeLogs);
        }

        upKeepTypeLogService.saveBatch(savedUpKeepTypeLogs);
    }

    private List<UpKeepLog> getUpKeepLogs(UpKeepReqVO reqVO, List<Machine> machines, Map<Integer, UpKeep> upKeepTable) {
        String url = JSONUtil.toJsonStr(reqVO.getUrl());
        Date now = new Date();
        AccountUser loginUser = LoginUserContextHolder.getLoginUser();

        List<Integer> machineIds = machines.stream().map(Machine::getMachineId).collect(Collectors.toList());
        Map<Integer, MachineUsageSumStat> machineUsageSumStatTable = machineUsageSumStatService.getListByMachineIds(reqVO.getAccountId(), machineIds)
                .stream()
                .collect(Collectors.toMap(MachineUsageSumStat::getMachineId, Function.identity()));

        List<Integer> accountIds = machines.stream().map(Machine::getAccountId).collect(Collectors.toList());
        Map<Integer, String> accountTable = accountService.getAccountIds(accountIds)
                .stream()
                .collect(Collectors.toMap(Account::getAccountId, Account::getName));

        Map<Integer, Integer> georeMachineTable = georeMachineService.getByMachineIds(machineIds)
                .stream()
                .collect(Collectors.toMap(GeoreMachine::getMachineId, GeoreMachine::getRegionId));

        Map<Integer, String> georeGionTable = georeGionService.getByRegionIds(georeMachineTable.values())
                .stream()
                .collect(Collectors.toMap(GeoreGion::getRegionId, GeoreGion::getRegionName));

        return machines.stream().map(machine -> {
            UpKeep upKeep = upKeepTable.getOrDefault(machine.getMachineId(), new UpKeep());
            MachineUsageSumStat machineUsageSumStat = machineUsageSumStatTable.getOrDefault(machine.getMachineId(), new MachineUsageSumStat());
            Integer regionId = georeMachineTable.get(machine.getMachineId());

            UpKeepLog upKeepLog = new UpKeepLog();
            upKeepLog.setAccountId(machine.getAccountId());
            upKeepLog.setAccountName(accountTable.get(machine.getAccountId()));
            upKeepLog.setRegionId(regionId);
            upKeepLog.setRegionName(georeGionTable.get(regionId));
            upKeepLog.setPhone(loginUser.getPhone());
            upKeepLog.setUsername(loginUser.getName());

            upKeepLog.setMachineId(machine.getMachineId());
            upKeepLog.setMachineType(machine.getMachineType());
            upKeepLog.setUserCode(machine.getUserCode());
            upKeepLog.setMachineNo(machine.getMachineNO());
            upKeepLog.setStatus(Optional.ofNullable(upKeep.getStatus()).orElse(0));
            upKeepLog.setUrl(url);
            upKeepLog.setMileage(Optional.ofNullable(upKeep.getMileage()).orElse(0));
            upKeepLog.setTime(DateUtil.between(Optional.ofNullable(upKeep.getDateTime()).orElse(now), now, DateUnit.DAY));

            // 计算总
            upKeepLog.setTotalMileage(Optional.ofNullable(machineUsageSumStat.getMileage()).orElse(0L));
            upKeepLog.setTotalTime(DateUtil.between(Optional.ofNullable(machineUsageSumStat.getDateTime()).orElse(now), now, DateUnit.DAY));

            upKeepLog.setCreator(String.valueOf(loginUser.getAccountUserId()));
            upKeepLog.setCreateTime(now);
            upKeepLog.setUpdater(String.valueOf(loginUser.getAccountUserId()));
            upKeepLog.setUpdateTime(now);
            upKeepLog.setRemark(reqVO.getRemark());

            return upKeepLog;
        }).collect(Collectors.toList());
    }

    /**
     * @description: 保养完成
     * @author: mo.shanyong
     * @date: 2025/4/23 16:32
     * @param: upKeepTable
     * @param: machines
     */

    private Map<Integer, UpKeep> completeUpKeep(Map<Integer, UpKeep> upKeepTable,List<Machine> machines){
        List<Machine> noExitMachineList = machines.stream().filter(machine -> {
            return !upKeepTable.containsKey(machine.getMachineId());
        }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(noExitMachineList)){
            return upKeepTable;
        }
        List<Integer> noExitMachineIdList = noExitMachineList.stream().map(Machine::getAccountId).distinct().collect(Collectors.toList());
        List<UpKeepConfig> upKeepConfigList = upKeepConfigDao.getByIds(noExitMachineIdList);
        Map<Integer, UpKeepConfig> upKeepConfigMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(upKeepConfigList)){
            upKeepConfigMap.putAll(upKeepConfigList.stream().collect(Collectors.toMap(UpKeepConfig::getAccountId,Function.identity())));
        }
        Date now = new Date();
        List<UpKeep> upKeeps = new ArrayList<>();
        Map<Integer, Integer> statusMap = new HashMap<>();
        for (Machine machine : noExitMachineList) {
            UpKeep upKeep = new UpKeep();
            upKeep.setAccountId(machine.getAccountId());
            upKeep.setMachineId(machine.getMachineId());
            upKeep.setMileage(0);
            upKeep.setDateTime(now);
            upKeep.setCreateTime(Optional.ofNullable(upKeep.getCreateTime()).orElse(now));
            upKeep.setUpdateTime(now);
            upKeep.setStatus(0);

            //计算从运营到现在的骑行里程数
            double machineMileage = this.getMachineMileage(machine.getMachineId(), machine.getJoinTime(), DateTimeUtil.getNowTime());
            //计算车辆的运营时间
            long between = DateUtil.between(DateTimeUtil.StringToDateTime(machine.getJoinTime()), now, DateUnit.DAY);
            statusMap.put(machine.getMachineId(),0);
            UpKeepConfig upKeepConfig = upKeepConfigMap.get(machine.getAccountId());
            // 里程超出阈值或者运营时间超出阈值，则是保养生效，需要记录生效状态，保证生效记录是有效的，但是当前车辆已经保养了，所以车辆的保养是无需保养
            if (upKeepConfig != null && ((Objects.equals(upKeepConfig.getMileageSwitch(), Constant.SWITCH_OPEN) && machineMileage > (upKeepConfig.getMileageLimit() * 1000d))
                    || (Objects.equals(upKeepConfig.getTimeSwitch(), Constant.SWITCH_OPEN) && between > upKeepConfig.getTimeLimit()))) {
                statusMap.put(machine.getMachineId(),1);
            }
            upKeeps.add(upKeep);
        }
        doPartitionInsertBatch(upKeepDao::insertBatch, upKeeps);
        //给保养的状态重新赋值，保证后面的保养记录是正确状态
        upKeepTable.putAll(upKeeps.stream().collect(Collectors.toMap(UpKeep::getMachineId,upKeep -> {
            upKeep.setStatus(statusMap.get(upKeep.getMachineId()));
            return upKeep;
        })));

        return upKeepTable;
    }

    /**
     * @description: 获取车辆的骑行区局
     * @author: mo.shanyong
     * @date: 2025/4/23 16:41
     * @param: accountId 区域id
     * @param: startTime 统计骑行里程开始时间
     * @param: endTime 统计骑行里程结束时间
     * @return: java.util.Map<java.lang.Integer, java.lang.Double>
     */
    private double getMachineMileage(Integer machineId,String startTime,String endTime){
        List<Map<String, Object>> machineUsageParamsList = new ArrayList<>();
        do {
            LocalDateTime startDateTime = DateTimeUtil.getLocalDateTimeByTimeStr(startTime);
            LocalDateTime endDateTime = DateTimeUtil.getLocalDateTimeByTimeStr(endTime);
            Map<String, Object> machineUsageParams = new HashMap<>();
            machineUsageParams.put(DaoParamConstant.machineId, machineId);
            machineUsageParams.put("startDt",startTime);
            //每次统计90天
            LocalDateTime localDateTime = startDateTime.plusDays(90);
            //如果加上8个小时后的时间段在最终时间的后面或者登录最终时间，则是最后一个时间段
            if (localDateTime.isAfter(endDateTime) || localDateTime.equals(endDateTime)){
                machineUsageParams.put("endDt",DateTimeUtil.getDateStrByLocalDateTime(endDateTime));
                machineUsageParamsList.add(machineUsageParams);
                break;
            }
            machineUsageParams.put("endDt",DateTimeUtil.getDateStrByLocalDateTime(localDateTime));
            machineUsageParamsList.add(machineUsageParams);
            startTime = DateTimeUtil.getDateStrByLocalDateTime(localDateTime);
        }while (true);

        double allMileage = 0;
        for (Map<String, Object> machineUsageParams : machineUsageParamsList) {
            // 1. 累计里程
            Double mileage = rideLogDetailsDao.selectMachineMileage(machineUsageParams);
            allMileage += (Objects.nonNull(mileage) ? mileage : 0);
        }
        return allMileage;
    }
    /**
     * @description: 分批进行操作
     * @author: mo.shanyong
     * @date: 2025/4/24 13:51
     * @param: consumer
     * @param: list
     */
    public <T> void doPartitionInsertBatch(Consumer<List<T>> consumer, List<T> list) {
        if (CollUtil.isNotEmpty(list)) {
            List<? extends List<T>> partition = cn.hutool.core.collection.ListUtil.partition(list, 500);
            partition.forEach(consumer);
        }
    }
}
